package robocup.component;

import java.util.Properties;

import robocup.component.infotypes.Info;
import robocup.connection.RCSSConnection;
import robocup.parser9.RobocupParser;

/**
 * This class is an abstractation for all agent types like player-agent
 * (RobocupAgent), coach (CoachAgent) and trainer (TrainerAgent) and thus is
 * extended by all these classes. It incorporates common attributes like
 * <code>HOST_NAME</code> for the network connection as well as methods -
 * abstract and implemented - for control flow of the agents. Since each agent
 * is an independent entity, this class extends <code>Thread
 * </code>.
 */
public abstract class AbstractAgent
    extends Thread {

  /**
   * Constants are used to identify items of the property-file, that should be
   * given on startup via command-line parameter.
   */
  public static final String HOST_NAME    = "hostname";

  public static final String HOST_PORT    = "port";

  public static final String MODE         = "mode";

  public static final String TEAM_NAME    = "teamname";

  /**
   * these constants are used to identify the type of agent, which is loaded.
   * The current type is stored in field <code>mode</code>
   */
  public static final int    FIELD_PLAYER = 0;

  public static final int    GOALIE       = 1;

  public static final int    COACH        = 2;

  public static final int    TRAINER      = 4;

  // The properties that are given in a file on startup via command line
  protected Properties       config       = null;

  protected int              serverPort   = 6000;

  protected String           serverHost   = "localhost";

  protected int              mode;

  protected String           teamName;

  protected boolean          isTeamEast;

  private boolean            running      = true;

  /**
   * All main components of the agent: connection: the client, handles send and
   * receive of messages parser: depending on the mode, a normal or coach-parser
   * world: the agents worldmodel synchro: a synchro component, calculating the
   * best time to send tactic: the action choosing component!
   */
  protected RCSSConnection   connection   = null;

  protected RobocupParser    parser       = null;

  /**
   * This class provides the basic features of an agent which connects to the
   * soccer-server. Implementing all abstract methods, subclasses are able to
   * define all possible types of agents (player/goalie/coach/trainer).
   * 
   * @param props -
   *          the agents configuration
   */
  public AbstractAgent(
      final Properties props) {

    super();
    this.config = props;
  }

  /**
   * This method initializes all basic configuration attributes, i.e. the host
   * and the port where the server is listening on connections.
   */
  protected void initBaseConfig() {

    String tmp = null;
    tmp = (String) this.config.get(AbstractAgent.TEAM_NAME);
    if (tmp != null) {
      this.teamName = tmp;
    }

    tmp = (String) this.config.get(AbstractAgent.HOST_NAME);
    if (tmp != null) {
      this.serverHost = tmp;
    }

    tmp = (String) this.config.get(AbstractAgent.HOST_PORT);
    if (tmp != null) {
      this.serverPort = Integer.parseInt(tmp);
    }

    tmp = (String) this.config.get(AbstractAgent.MODE);
    if (tmp != null) {
      this.mode = Integer.parseInt(tmp);
    }

    // call to the subclass initialization
    this.initConfig();
  }

  /**
   * This method initializes the RCSSConnection by using the host and port
   * defined in initBaseConnection().
   */
  private boolean initConnection() {

    System.out
        .println("[RobocupAgent] Try to connect agent to Server: " + this.serverHost + ":" + this.serverPort);
    if (this.connection == null) {
      this.connection = new RCSSConnection(this.serverHost, this.serverPort);
    }
    // calling the connect of the subclass
    return this.initProtocol();
  }

  /**
   * This method initializes all neccessary data of the player. This should be
   * done always before starting the thread!
   */
  public void initialize() {

    // initialize all
    this.initBaseConfig();
    this.initComponents();
    boolean b = false;
    while (!b) {
      b = this.initConnection();
    }
  }

  /**
   * This method overrides the inherited method from <code>Thread</code> by
   * adding a basic mechanism. This includes receiving messages from the server
   * and parsing them into Info-Objects. These objects are forwarded to the
   * abstract method <code>processInfo(Info)</code>, which is implemented in
   * the extending classes.
   */
  @Override
  public void run() {

    this.setName("Agent-Thread " + System.currentTimeMillis());
    this.running = true;
    while (this.running) {
      try {
        // read data from connection
        final String str = this.connection.receive();
        // if a timeout occurs
        if (str == null) {
          System.out.println("Agent terminated!");
          return;
        }

        // parse it, if string is available
        Info info = null;
        if (str != null && str.length() > 1 &&
        		!(this instanceof TrainerAgent)) {
          try {
        	info = this.parser.parseCommand(str);
          } catch (Exception e) {
        	  System.out.println("ERROR PARSING MESSAGE: " + str);
        	  e.printStackTrace();
          }
        }

        if (info != null) {
          this.processInfo(info);
        }
      }
      catch (final Exception e) {
        System.out.println("Error in Agent process:");
        e.printStackTrace();
      }
    }
  }

  /**
   * These method should be overriden by all subclasses to implement the logic
   * of information handling, which is very different for different types of
   * agents (e.g. for players, who have to be synchronized).
   * 
   * @param info -
   *          the received info-object
   */
  protected abstract void processInfo(Info info);

  /**
   * This method initializes all necessary data, reading it from the properties
   * given for that agent.
   */
  protected abstract void initConfig();

  /**
   * This method handles the initialization protocol, depending on the
   * subclass-agent. I.e., a RobocupAgent (Player/Goalie) sends an init-message
   * and retrieves serverconfigs, playerconfigs, etc.
   * 
   * @return - true, if protocol was successfully executed!
   */
  protected abstract boolean initProtocol();

  /**
   * This method should be overwritten to initialize all necessary components of
   * the agent, i.e. the parser, the worldmodel, the tactic, etc.
   */
  protected abstract void initComponents();

  /**
   * This method sends a string to the soccerserver, and should only be invoked
   * by the synchro-component. The string is forwarded to the RCSSConnection,
   * which sends the string immediately as UDP packet!
   * 
   * @param action -
   *          the action string
   */
  public void sendActions(final String action) {

    try {
      this.connection.send(action);
    }
    catch (final Exception e) {
      System.out.println("ERROR in : AbstractAgent.sendActions");
      e.printStackTrace();
    }
  }

  // ~ --------------------------------------------
  // ~ Methods for accessing the agents type and location

  /**
   * retrieves the teamName
   */
  public String getTeamName() {

    return this.teamName;
  }

  /**
   * Sets the teamName
   * 
   * @param teamName
   */
  public void setTeamName(final String teamName) {

    this.teamName = teamName;
  }

  /**
   * Tests, if team is east side starting
   * 
   * @return true, if the team is started on east side
   */
  public boolean isTeamEast() {

    return this.isTeamEast;
  }

  /**
   * Sets the side of the team
   * 
   * @param is -
   *          true, if team plays on east side
   */
  public void setTeamEast(final boolean is) {

    this.isTeamEast = is;
  }

  /**
   * Checks if the mode is online coach
   */
  public boolean isOnline() {

    return (this.mode == AbstractAgent.COACH);
  }

  /**
   * Retrieves the mode of the player (one of TRAINER, COACH, GOALIE or FIELD)
   * 
   * @return
   */
  public int getMode() {

    return this.mode;
  }

  /**
   * This method makes the player disconnect from the server and stopping the
   * thread. This will cause the player to be killed.
   */
  public void disconnect() {

    this.running = false;
    try {
      this.connection.send("(bye)");
      System.out.println(this.connection.receive());
    }
    catch (final Exception e) {
      e.printStackTrace();
    }
  }
}
